#include <math.h>
#include <stdint.h>

#define INTERNAL static
#define LOCAL_PERSIST static
#define GLOBAL_VARIABLE static

#define Pi32 3.14159265359f

typedef uint8_t uint8;
typedef uint16_t unint16;
typedef uint32_t uint32;
typedef uint64_t uint64;

typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef int32 bool32;

typedef float real32;
typedef double real64;

#include "handmade_hero.cpp"

#include <windows.h>
#include <stdio.h>
#include <malloc.h>
#include <xinput.h>
#include <dsound.h>

#include "win32_handmade_hero.h"

// TODO: This is a global for now.
GLOBAL_VARIABLE bool32 gRunning;
GLOBAL_VARIABLE bool32 gPause;
GLOBAL_VARIABLE win32_offscreen_buffer gBackbuffer;
GLOBAL_VARIABLE LPDIRECTSOUNDBUFFER gSecondaryBuffer;
GLOBAL_VARIABLE int64 gPerfCountFrequency;

// XInputGetState
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState) // 方便定义多个具有相同函数签名的函数
typedef X_INPUT_GET_STATE(x_input_get_state);
X_INPUT_GET_STATE(XInputGetStateStub)
{
    return ERROR_DEVICE_NOT_CONNECTED;
}
GLOBAL_VARIABLE x_input_get_state *gXInputGetState_ = XInputGetStateStub;
#define XInputGetState gXInputGetState_

// XInputSetState
#define X_INPUT_SET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)
typedef X_INPUT_SET_STATE(x_input_set_state);
X_INPUT_SET_STATE(XInputSetStateStub)
{
    return ERROR_DEVICE_NOT_CONNECTED;
}
GLOBAL_VARIABLE x_input_set_state *XInputSetState_ = XInputSetStateStub;
#define XInputSetState XInputSetState_

#define DIRECT_SOUND_CREATE(name) HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter)
typedef DIRECT_SOUND_CREATE(direct_sound_create);

INTERNAL debug_read_file_result DEBUGPlatformReadEntireFile(char *filename)
{
    debug_read_file_result result = {};

    HANDLE fileHandle = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    if (fileHandle != INVALID_HANDLE_VALUE)
    {
        LARGE_INTEGER fileSize;
        if (GetFileSizeEx(fileHandle, &fileSize))
        {
            uint32 fileSize32 = SafeTruncateUInt64(fileSize.QuadPart);
            result.contents = VirtualAlloc(0, fileSize32, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
            if (result.contents)
            {
                DWORD bytesRead;
                if (ReadFile(fileHandle, result.contents, fileSize32, &bytesRead, 0) && (fileSize32 == bytesRead))
                {
                    result.contentSize = fileSize32;
                }
                else
                {
                    DEBUGPlatformFreeFileMemory(result.contents);
                    result.contents = 0;
                }
            }
            else
            {
                // TODO: Logging
            }
        }
        else
        {
            // TODO: Logging
        }

        CloseHandle(fileHandle);
    }
    else
    {
        // TODO: Logging
    }

    return result;
}

INTERNAL void DEBUGPlatformFreeFileMemory(void *memory)
{
    if (memory)
    {
        VirtualFree(memory, 0, MEM_RELEASE);
    }
}

INTERNAL bool32 DEBUGPlatformWriteEntireFile(char *filename, uint32 memorySize, void *memory)
{
    bool32 result = false;

    HANDLE fileHandle = CreateFileA(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
    if (fileHandle != INVALID_HANDLE_VALUE)
    {
        DWORD bytesWritten;
        if (WriteFile(fileHandle, memory, memorySize, &bytesWritten, 0))
        {
            result = (bytesWritten == memorySize);
        }
        else
        {
            // TODO: Logging
        }

        CloseHandle(fileHandle);
    }
    else
    {
        // TODO: Loggind
    }

    return result;
}

INTERNAL void Win32LoadXInput(void)
{
    HMODULE hXInputLibrary = LoadLibraryA("xinput1_4.dll");
    if (!hXInputLibrary)
    {
        hXInputLibrary = LoadLibraryA("XInput9_1_0.dll");
    }

    if (!hXInputLibrary)
    {
        hXInputLibrary = LoadLibraryA("xinput1_3.dll");
    }

    if (hXInputLibrary)
    {
        XInputGetState = (x_input_get_state *)GetProcAddress(hXInputLibrary, "XInputGetState");
        if (!XInputGetState)
        {
            XInputGetState = XInputGetStateStub;
        }

        XInputSetState = (x_input_set_state *)GetProcAddress(hXInputLibrary, "XInputSetState");
        if (!XInputSetState)
        {
            XInputSetState = XInputSetStateStub;
        }

        // TODO: Diagnostic
    }
    else
    {
        // TOCO: Diagnostic
    }
}

INTERNAL void Win32InitDSound(HWND hWindow, int32 samplesPerSecond, int32 bufferSize)
{
    // Load the library
    HMODULE hDSoundLibrary = LoadLibraryA("dsound.dll");

    if (hDSoundLibrary)
    {
        // Get a DirectSound object - cooperative
        direct_sound_create *directSoundCreate =
            (direct_sound_create *)GetProcAddress(hDSoundLibrary, "DirectSoundCreate");

        LPDIRECTSOUND directSound;
        if (directSoundCreate && SUCCEEDED(directSoundCreate(0, &directSound, 0)))
        {
            WAVEFORMATEX waveFormat = {};
            waveFormat.wFormatTag = WAVE_FORMAT_PCM;
            waveFormat.nChannels = 2;
            waveFormat.nSamplesPerSec = samplesPerSecond;
            waveFormat.wBitsPerSample = 16;
            waveFormat.nBlockAlign = (waveFormat.nChannels * waveFormat.wBitsPerSample) / 8;
            waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
            waveFormat.cbSize = 0;

            if (SUCCEEDED(directSound->SetCooperativeLevel(hWindow, DSSCL_PRIORITY)))
            {
                DSBUFFERDESC bufferDescription = {};
                bufferDescription.dwSize = sizeof(bufferDescription);
                bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;

                // Create a primary buffer
                LPDIRECTSOUNDBUFFER primaryBuffer;
                if (SUCCEEDED(directSound->CreateSoundBuffer(&bufferDescription, &primaryBuffer, 0)))
                {
                    HRESULT error = primaryBuffer->SetFormat(&waveFormat);
                    if (SUCCEEDED(error))
                    {
                        OutputDebugStringA("Primary buffer format was set.\n");
                    }
                    else
                    {
                        // TODO: Diagnostic
                    }
                }
                else
                {
                    // TODO: Diagnostic
                }
            }
            else
            {
                // TODO: Diagnostic
            }

            // Create a secondary buffer
            DSBUFFERDESC bufferDescription = {};
            bufferDescription.dwSize = sizeof(bufferDescription);
            bufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
            bufferDescription.dwBufferBytes = bufferSize;
            bufferDescription.lpwfxFormat = &waveFormat;
            HRESULT error = directSound->CreateSoundBuffer(&bufferDescription, &gSecondaryBuffer, 0);
            if (SUCCEEDED(error))
            {
                // Start it playing!
                OutputDebugStringA("Secondary buffer created successfully.\n");
            }
        }
        else
        {
        }
    }
}

INTERNAL void Win32ClearBuffer(win32_sound_output &soundOutput)
{
    VOID *region1;
    DWORD region1Size;
    VOID *region2;
    DWORD region2Size;
    if (SUCCEEDED(gSecondaryBuffer->Lock(0, soundOutput.secondaryBufferSize,
                                         &region1, &region1Size, &region2, &region2Size, 0)))
    {
        uint8 *destSample = (uint8 *)region1;
        for (DWORD byteIndex = 0; byteIndex < region1Size; ++byteIndex)
        {
            *destSample++ = 0;
        }

        destSample = (uint8 *)region2;
        for (DWORD byteIndex = 0; byteIndex < region2Size; ++byteIndex)
        {
            *destSample++ = 0;
        }

        gSecondaryBuffer->Unlock(region1, region1Size, region2, region2Size);
    }
}

INTERNAL void Win32FillSoundBuffer(win32_sound_output &soundOutput, DWORD byteToLock, DWORD bytesToWrite,
                                   game_sound_output_buffer &sourceBuffer)
{
    // int16  int16      int16 int16  int16 int16 int16  int16 ......
    // [左声道 右声道]    左声道 右声道 左声道 右声道 左声道 右声道 ......
    // [一个采样(Sample)]
    VOID *region1;
    DWORD region1Size;
    VOID *region2;
    DWORD region2Size;
    if (SUCCEEDED(gSecondaryBuffer->Lock(byteToLock, bytesToWrite,
                                         &region1, &region1Size, &region2, &region2Size, 0)))
    {
        // TODO: assert that Region1Size/Region2Size is valid
        // TODO: Collapse these two loops
        DWORD region1SampleCount = region1Size / soundOutput.bytesPerSample;
        int16 *destSample = (int16 *)region1;
        int16 *sourceSample = sourceBuffer.samples;
        for (DWORD sampleIndex = 0; sampleIndex < region1SampleCount; ++sampleIndex)
        {
            *destSample++ = *sourceSample++; // 左声道
            *destSample++ = *sourceSample++; // 右声道
            ++soundOutput.runningSampleIndex;
        }

        destSample = (int16 *)region2;
        DWORD region2SampleCount = region2Size / soundOutput.bytesPerSample;
        for (DWORD sampleIndex = 0; sampleIndex < region2SampleCount; ++sampleIndex)
        {
            *destSample++ = *sourceSample++; // 左声道
            *destSample++ = *sourceSample++; // 右声道
            ++soundOutput.runningSampleIndex;
        }
        gSecondaryBuffer->Unlock(region1, region1Size, region2, region2Size);
    }
}

INTERNAL void Win32ProcessXInputDigitalButton(DWORD xInputButtonState,
                                              game_button_state *oldState, DWORD buttonBit,
                                              game_button_state *newState)
{
    newState->endedDown = ((xInputButtonState & buttonBit) == buttonBit);
    newState->halfTransitionCount = (oldState->endedDown != newState->endedDown) ? 1 : 0;
}

INTERNAL real32 Win32ProcessXInputStickValue(SHORT value, SHORT deadZoneThreshold)
{
    real32 result = 0;

    if (value < -deadZoneThreshold)
    {
        result = (real32)((value + deadZoneThreshold) / (32768.0f - deadZoneThreshold));
    }
    else if (value > deadZoneThreshold)
    {
        result = (real32)((value - deadZoneThreshold) / (32767.0f - deadZoneThreshold));
    }

    return result;
}

INTERNAL void Win32ProcessKeyboardMessage(game_button_state *newState, bool32 isDown)
{
    // 状态改变时才会收到这个键盘消息
    Assert(newState->endedDown != isDown);
    newState->endedDown = isDown;
    ++newState->halfTransitionCount;
}

INTERNAL win32_window_dimension Win32GetWindowDimension(HWND hWindow)
{
    win32_window_dimension result;

    RECT clientRect;
    GetClientRect(hWindow, &clientRect);
    result.width = clientRect.right - clientRect.left;
    result.height = clientRect.bottom - clientRect.top;

    return result;
}

INTERNAL void Win32ResizeDIBSection(win32_offscreen_buffer &buffer, int width, int height)
{
    // TODO: Maybe don't free first, free after, then free first if that fails.

    if (buffer.memory)
    {
        VirtualFree(buffer.memory, 0, MEM_RELEASE);
    }

    buffer.width = width;
    buffer.height = height;

    int bytesPerPixel = 4;
    buffer.bytesPerPixel = bytesPerPixel;

    // When the biHeight field is negative, this is the clue to
    // Windows to treat this bitmap as top-down, not bottom-up, meaning that
    // the first three bytes of the image are the color for the top left pixel
    // in the bitmap, not the bottom left!
    buffer.info.bmiHeader.biSize = sizeof(buffer.info.bmiHeader);
    buffer.info.bmiHeader.biWidth = buffer.width;
    buffer.info.bmiHeader.biHeight = -buffer.height;
    buffer.info.bmiHeader.biPlanes = 1;
    buffer.info.bmiHeader.biBitCount = 32; // 每个像素RGB占3个字节，我们用padding来4字节对齐
    buffer.info.bmiHeader.biCompression = BI_RGB;

    // NOTE(casey): Thank you to Chris Hecker of Spy Party fame
    // for clarifying the deal with StretchDIBits and BitBlt!
    // No more Dc for us.
    int bitmapMemorySize = (buffer.width * buffer.height) * bytesPerPixel;
    buffer.memory = VirtualAlloc(0, bitmapMemorySize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    buffer.pitch = width * bytesPerPixel;
}

INTERNAL void Win32DisplayBufferInWindow(const win32_offscreen_buffer &buffer,
                                         HDC deviceContext, int windowWidth, int windowHeight)
{
    // TODO: Aspect ratio correction
    StretchDIBits(deviceContext,
                  0, 0, windowWidth, windowHeight,
                  0, 0, buffer.width, buffer.height,
                  buffer.memory,
                  &buffer.info,
                  DIB_RGB_COLORS, SRCCOPY);
}

// 处理来自窗口的所有消息
INTERNAL LRESULT CALLBACK Win32MainWindowCallback(
    HWND hWindow,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    LRESULT result = 0;
    switch (message)
    {
    case WM_SIZE:
    {
    }
    break;

    case WM_CLOSE:
    {
        // TODO: Handle this with a message to the user?
        gRunning = false;
    }
    break;

    case WM_ACTIVATEAPP:
    {
        OutputDebugStringA("WM_ACTIVATEAPP\n");
    }
    break;

    case WM_DESTROY:
    {
        // TODO: Handle this as an error - recreate window?
        gRunning = false;
    }
    break;

    case WM_SYSKEYDOWN:
    case WM_SYSKEYUP:
    case WM_KEYDOWN:
    case WM_KEYUP:
    {
        Assert(!"Keyboard input came in through a non-dispatch message!");
    }
    break;

    case WM_PAINT:
    {
        PAINTSTRUCT paint;
        HDC deviceContext = BeginPaint(hWindow, &paint);
        win32_window_dimension dimension = Win32GetWindowDimension(hWindow);
        Win32DisplayBufferInWindow(gBackbuffer, deviceContext, dimension.width, dimension.height);
        EndPaint(hWindow, &paint);
    }
    break;

    default:
    {
        result = DefWindowProc(hWindow, message, wParam, lParam);
    }
    break;
    }

    return result;
}

INTERNAL void Win32ProcessPendingMessages(game_controller_input *keyboardController)
{
    MSG message;
    while (PeekMessageA(&message, 0, 0, 0, PM_REMOVE))
    {
        switch (message.message)
        {
        case WM_QUIT:
        {
            gRunning = false;
        }
        break;

        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:
        case WM_KEYDOWN:
        case WM_KEYUP:
        {
            // wParam就是虚拟键码
            uint32 VKCode = (uint32)message.wParam;
            bool32 wasDown = ((message.lParam & (1 << 30)) != 0);
            bool32 isDown = ((message.lParam & (1 << 31)) == 0);

            if (wasDown != isDown)
            {
                if (VKCode == 'W')
                {
                    Win32ProcessKeyboardMessage(&keyboardController->moveUp, isDown);
                }
                else if (VKCode == 'A')
                {
                    Win32ProcessKeyboardMessage(&keyboardController->moveLeft, isDown);
                }
                else if (VKCode == 'S')
                {
                    Win32ProcessKeyboardMessage(&keyboardController->moveDown, isDown);
                }
                else if (VKCode == 'D')
                {
                    Win32ProcessKeyboardMessage(&keyboardController->moveRight, isDown);
                }
                else if (VKCode == 'Q')
                {
                    Win32ProcessKeyboardMessage(&keyboardController->leftShoulder, isDown);
                }
                else if (VKCode == 'E')
                {
                    Win32ProcessKeyboardMessage(&keyboardController->rightShoulder, isDown);
                }
                else if (VKCode == VK_UP)
                {
                    Win32ProcessKeyboardMessage(&keyboardController->actionUp, isDown);
                }
                else if (VKCode == VK_DOWN)
                {
                    Win32ProcessKeyboardMessage(&keyboardController->actionDown, isDown);
                }
                else if (VKCode == VK_LEFT)
                {
                    Win32ProcessKeyboardMessage(&keyboardController->actionLeft, isDown);
                }
                else if (VKCode == VK_RIGHT)
                {
                    Win32ProcessKeyboardMessage(&keyboardController->actionRight, isDown);
                }
                else if (VKCode == VK_ESCAPE)
                {
                    Win32ProcessKeyboardMessage(&keyboardController->start, isDown);
                }
                else if (VKCode == VK_SPACE)
                {
                    Win32ProcessKeyboardMessage(&keyboardController->back, isDown);
                }
#if HANDMADE_INTERNAL
                else if (VKCode == 'P')
                {
                    if (isDown)
                    {
                        gPause = !gPause;
                    }
                }
#endif
            }
            // Alt+F4关闭
            bool32 AltKeyWasDown = ((message.lParam & (1 << 29)) != 0);
            if ((VKCode == VK_F4) && AltKeyWasDown)
            {
                gRunning = false;
            }
        }
        break;

        default:
        {
            TranslateMessage(&message);
            DispatchMessage(&message);
        }
        break;
        }
    }
}

inline LARGE_INTEGER Win32GetWallClock(void)
{
    LARGE_INTEGER result;
    QueryPerformanceCounter(&result);
    return result;
}

inline real32 Win32GetSecondsElapsed(LARGE_INTEGER start, LARGE_INTEGER end)
{
    real32 result = ((real32)(end.QuadPart - start.QuadPart) / (real32)gPerfCountFrequency);
    return result;
}

INTERNAL void Win32DebugDrawVertical(win32_offscreen_buffer *backbuffer, int x, int top, int bottom, uint32 color)
{
    if (top <= 0)
    {
        top = 0;
    }

    if (bottom > backbuffer->height)
    {
        bottom = backbuffer->height;
    }

    if ((x >= 0) && (x < backbuffer->width))
    {
        uint8 *pixel = ((uint8 *)backbuffer->memory + x * backbuffer->bytesPerPixel + top * backbuffer->pitch);
        for (int y = top; y < bottom; ++y)
        {
            *(uint32 *)pixel = color;
            pixel += backbuffer->pitch;
        }
    }
}

inline void Win32DrawSoundBufferMarker(win32_offscreen_buffer *backbuffer, win32_sound_output *soundOutput, real32 c,
                                       int padX, int top, int bottom, DWORD value, uint32_t color)
{
    real32 xReal32 = (c * (real32)value);
    int x = padX + (int)xReal32;
    Win32DebugDrawVertical(backbuffer, x, top, bottom, color);
}

INTERNAL void Win32DebugSyncDisplay(win32_offscreen_buffer *backbuffer, int markerCount, win32_debug_time_marker *markers,
                                    int currentMarkerIndex,
                                    win32_sound_output *soundOutput, real32 targetSecondsPerFrame)
{
    int padX = 16;
    int padY = 16;

    int lineHeight = 64;

    real32 c = (real32)(backbuffer->width - 2 * padX) / (real32)soundOutput->secondaryBufferSize;
    for (int markerIndex = 0; markerIndex < markerCount; ++markerIndex)
    {
        win32_debug_time_marker *thisMarker = &markers[markerIndex];
        Assert(thisMarker->outputPlayCursor < soundOutput->secondaryBufferSize);
        Assert(thisMarker->outputWriteCursor < soundOutput->secondaryBufferSize);
        Assert(thisMarker->outputLocation < soundOutput->secondaryBufferSize);
        Assert(thisMarker->outputByteCount < soundOutput->secondaryBufferSize);
        Assert(thisMarker->outputPlayCursor < soundOutput->secondaryBufferSize);
        Assert(thisMarker->outputWriteCursor < soundOutput->secondaryBufferSize);

        DWORD playColor = 0xFFFFFFFF;
        DWORD writeColor = 0xFFFF0000;
        DWORD expectedFlipColor = 0xFFFFFF00;
        DWORD playWindowColor = 0xFFFF00FF;

        int top = padY;
        int bottom = padY + lineHeight;
        if (markerIndex == currentMarkerIndex)
        {
            top += lineHeight + padY;
            bottom += lineHeight + padY;

            int firstTop = top;

            Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, top, bottom, thisMarker->outputPlayCursor, playColor);
            Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, top, bottom, thisMarker->outputWriteCursor, writeColor);

            top += lineHeight + padY;
            bottom += lineHeight + padY;

            Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, top, bottom, thisMarker->outputLocation, playColor);
            Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, top, bottom, thisMarker->outputLocation + thisMarker->outputByteCount, writeColor);

            top += lineHeight + padY;
            bottom += lineHeight + padY;

            Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, firstTop, bottom, thisMarker->expectedFlipPlayCursor, expectedFlipColor);
        }

        Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, top, bottom, thisMarker->flipPlayCursor, playColor);
        Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, top, bottom, thisMarker->flipPlayCursor + 480 * soundOutput->bytesPerSample, playWindowColor);
        Win32DrawSoundBufferMarker(backbuffer, soundOutput, c, padX, top, bottom, thisMarker->flipWriteCursor, writeColor);
    }
}

int CALLBACK WinMain(
    _In_ HINSTANCE instance,
    _In_opt_ HINSTANCE prevInstance,
    _In_ LPSTR cmdLine,
    _In_ int showCmd)
{
    LARGE_INTEGER perfCountFrequencyResult;
    QueryPerformanceFrequency(&perfCountFrequencyResult);
    gPerfCountFrequency = perfCountFrequencyResult.QuadPart;

    // NOTE: Set the Windows scheduler granularity(粒度) to 1ms
    // so that our sleep() can be more granular(更细).
    UINT desiredSchedulerMS = 1;
    bool32 sleepIsGranular = (timeBeginPeriod(desiredSchedulerMS) == TIMERR_NOERROR);

    Win32LoadXInput();

    WNDCLASSA windowClass = {};

    Win32ResizeDIBSection(gBackbuffer, 1280, 720);

    windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // 窗口样式
    windowClass.lpfnWndProc = Win32MainWindowCallback;      // 指向定义了窗口如何响应事件的函数
    windowClass.hInstance = instance;                       // 设置此窗口的实例的句柄,可使用GetModuleHandle(0)获取
    windowClass.lpszClassName = "HandmadeHeroWindowClass";  // 窗口类名称

    const int monitorRefreshHz = 60;
    const int gameUpdateHz = monitorRefreshHz / 2;
    real32 targetSecondsPerFrame = 1.0f / (real32)gameUpdateHz;

    if (RegisterClassA(&windowClass)) // 注册我们定义的窗口类
    {
        HWND hWindow = CreateWindowExA(0, windowClass.lpszClassName, "Handmade Hero",
                                       WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                                       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                                       0, 0, instance, 0);
        if (hWindow)
        {
            // NOTE(casey): Since we specified cS_OWNDc, we can just
            // get one device context and use it forever because we
            // are not sharing it with anyone.
            HDC deviceContext = GetDC(hWindow);
            win32_sound_output soundOutput = {0};

            soundOutput.samplesPerSecond = 48000;
            soundOutput.bytesPerSample = sizeof(int16) * 2; // 一个Sample包含两个声道
            soundOutput.secondaryBufferSize = soundOutput.samplesPerSecond * soundOutput.bytesPerSample;
            soundOutput.latencySampleCount = 3 * (soundOutput.samplesPerSecond / gameUpdateHz);
            soundOutput.safetyBytes = (soundOutput.samplesPerSecond * soundOutput.bytesPerSample / gameUpdateHz) / 3;
            Win32InitDSound(hWindow, soundOutput.samplesPerSecond, soundOutput.secondaryBufferSize);
            Win32ClearBuffer(soundOutput);
            gSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);

            gRunning = true;
#if 0
            // NOTE(casey): This tests the PlayCursor/WritecÃrsor update frequency
            // On the Handmade Hero machine, it was 480 samples.
            while (gRunning)
            {
                DWORD playCursor;
                DWORD writeCursor;
                gSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor);

                char textBuffer[256];
                _snprintf_s(textBuffer, sizeof(textBuffer), "PC: %u WC:%u\n", playCursor, writeCursor);
                OutputDebugStringA(textBuffer);
            }
#endif

            // TODO: Pool with bitmap VirtualAlloc
            // 游戏可以填充样本的地方，之后，我们会将这些样本复制到环形缓冲区

            int16 *samples = (int16 *)VirtualAlloc(0, soundOutput.secondaryBufferSize,
                                                   MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

#if HANDMADE_INTERNAL
            LPVOID baseAddress = (LPVOID)Terabytes((uint64)2);
#else
            LPVOID baseAddress = 0;
#endif
            game_memory gameMemory = {};
            gameMemory.permanentStorageSize = Megabytes(64);
            gameMemory.transientStorageSize = Gigabytes(1);

            uint64 totalSize = gameMemory.permanentStorageSize + gameMemory.transientStorageSize;
            gameMemory.permanentStorage = VirtualAlloc(baseAddress, (size_t)totalSize,
                                                       MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
            gameMemory.transientStorage = ((uint8 *)gameMemory.permanentStorage + gameMemory.permanentStorageSize);

            if (samples && gameMemory.permanentStorage && gameMemory.transientStorage)
            {
                game_input input[2] = {};
                game_input *newInput = &input[0];
                game_input *oldInput = &input[1];

                LARGE_INTEGER lastCounter = Win32GetWallClock();
                LARGE_INTEGER flipWallClock = lastCounter;

                int debugTimeMarkerIndex = 0;
                win32_debug_time_marker debugTimeMarkers[gameUpdateHz / 2] = {0};

                DWORD audioLatencyBytes = 0;
                real32 audioLatencySeconds = 0;
                bool32 soundIsValid = false;
                uint64 lastCycleCount = __rdtsc();
                // 消息循环
                while (gRunning)
                {
                    game_controller_input *oldKeyboardController = GetController(oldInput, 0);
                    game_controller_input *newKeyboardController = GetController(newInput, 0);
                    *newKeyboardController = {};
                    newKeyboardController->isConnected = true;
                    for (int buttonIndex = 0; buttonIndex < ArrayCount(newKeyboardController->buttons); ++buttonIndex)
                    {
                        newKeyboardController->buttons[buttonIndex].endedDown =
                            oldKeyboardController->buttons[buttonIndex].endedDown;
                    }

                    Win32ProcessPendingMessages(newKeyboardController);

                    if (!gPause)
                    {
                        DWORD maxControllerCount = XUSER_MAX_COUNT;
                        if (maxControllerCount > (ArrayCount(newInput->controllers) - 1))
                        {
                            maxControllerCount = (ArrayCount(newInput->controllers) - 1);
                        }

                        // 手柄轮询，最多支持4个手柄
                        for (DWORD controllerIndex = 0; controllerIndex < maxControllerCount; ++controllerIndex)
                        {
                            DWORD ourControllerIndex = controllerIndex + 1;
                            game_controller_input *oldController = GetController(oldInput, ourControllerIndex);
                            game_controller_input *newController = GetController(newInput, ourControllerIndex);

                            XINPUT_STATE ControllerState;
                            if (XInputGetState(controllerIndex, &ControllerState) == ERROR_SUCCESS)
                            {
                                newController->isConnected = true;

                                // NOTE: This controller is plugged in
                                // TODO: See if ControllerState.dwPacketNumber increments too rapidly
                                XINPUT_GAMEPAD *pad = &ControllerState.Gamepad;

                                newController->stickAverageX =
                                    Win32ProcessXInputStickValue(pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
                                newController->stickAverageY =
                                    Win32ProcessXInputStickValue(pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
                                if ((newController->stickAverageX != 0.0f) || (newController->stickAverageY != 0.0f))
                                {
                                    newController->isAnalog = true;
                                }

                                if (pad->wButtons & XINPUT_GAMEPAD_DPAD_UP)
                                {
                                    newController->stickAverageY = 1.0f;
                                    newController->isAnalog = false;
                                }
                                if (pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
                                {
                                    newController->stickAverageY = -1.0f;
                                    newController->isAnalog = false;
                                }
                                if (pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
                                {
                                    newController->stickAverageX = -1.0f;
                                    newController->isAnalog = false;
                                }
                                if (pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
                                {
                                    newController->stickAverageX = 1.0f;
                                    newController->isAnalog = false;
                                }

                                real32 threshold = 0.5f;
                                Win32ProcessXInputDigitalButton((newController->stickAverageX < -threshold) ? 1 : 0,
                                                                &oldController->moveLeft, 1, &newController->moveLeft);
                                Win32ProcessXInputDigitalButton((newController->stickAverageX > threshold) ? 1 : 0,
                                                                &oldController->moveRight, 1, &newController->moveRight);
                                Win32ProcessXInputDigitalButton((newController->stickAverageY < -threshold) ? 1 : 0,
                                                                &oldController->moveDown, 1, &newController->moveDown);
                                Win32ProcessXInputDigitalButton((newController->stickAverageY > threshold) ? 1 : 0,
                                                                &oldController->moveUp, 1, &newController->moveUp);

                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->actionDown, XINPUT_GAMEPAD_A,
                                                                &newController->actionDown);
                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->actionRight, XINPUT_GAMEPAD_B,
                                                                &newController->actionRight);
                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->actionLeft, XINPUT_GAMEPAD_X,
                                                                &newController->actionLeft);
                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->actionUp, XINPUT_GAMEPAD_Y,
                                                                &newController->actionUp);
                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->leftShoulder, XINPUT_GAMEPAD_LEFT_SHOULDER,
                                                                &newController->leftShoulder);
                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->rightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER,
                                                                &newController->rightShoulder);

                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->start, XINPUT_GAMEPAD_START,
                                                                &newController->start);
                                Win32ProcessXInputDigitalButton(pad->wButtons,
                                                                &oldController->back, XINPUT_GAMEPAD_BACK,
                                                                &newController->back);
                            }
                            else
                            {
                                // The controller is not available
                                newController->isConnected = false;
                            }
                        }

                        game_offscreen_buffer buffer = {};
                        buffer.memory = gBackbuffer.memory;
                        buffer.width = gBackbuffer.width;
                        buffer.height = gBackbuffer.height;
                        buffer.pitch = gBackbuffer.pitch;
                        GameUpdateAndRender(&gameMemory, newInput, &buffer);

                        LARGE_INTEGER audioWallClock = Win32GetWallClock();
                        real32 fromBeginToAudioSeconds = Win32GetSecondsElapsed(flipWallClock, audioWallClock);

                        DWORD playCursor;
                        DWORD writeCursor;
                        if (gSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor) == DS_OK)
                        {
                            /* NOTE:
                                Here is how sound output computation works.
                                We define a safety value that is the number
                                of samples we think our game update loop
                                may vary by (let's say up to 2ms)

                                When we wake up to write audio, we will look
                                and see what the play cursor position is and we
                                will forecast ahead where we think the play
                                cursor will be on the next frame boundary.

                                We will then look to see if the write cursor is
                                before that by at least our safety value. If
                                it is, the target fill position is that frame
                                boundary plus one frame. This gives us perfect
                                audio sync in the case of a audio card that has low
                                enough latency.

                                If the write cursor is _after_ that safety
                                margin, then we assume we can never sync the
                                audio perfectly, so we will write one frame's
                                worth of audio plus the safety margin's worth
                                of guard samples.
                            */
                            if (!soundIsValid)
                            {
                                soundOutput.runningSampleIndex = writeCursor / soundOutput.bytesPerSample;
                                soundIsValid = true;
                            }

                            DWORD byteToLock = ((soundOutput.runningSampleIndex * soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize);

                            DWORD expectedSoundBytesPerFrame =
                                (soundOutput.samplesPerSecond * soundOutput.bytesPerSample) / gameUpdateHz;
                            real32 secondsLeftUntilFlip = (targetSecondsPerFrame - fromBeginToAudioSeconds);
                            DWORD expectedBytesUntilFlip = (DWORD)((secondsLeftUntilFlip / targetSecondsPerFrame) * (real32)expectedSoundBytesPerFrame);
                            
                            DWORD expectedFrameBoundaryByte = playCursor + expectedSoundBytesPerFrame;

                            DWORD safeWriteCursor = writeCursor;
                            if (safeWriteCursor < playCursor)
                            {
                                safeWriteCursor += soundOutput.secondaryBufferSize;
                            }
                            Assert(safeWriteCursor >= playCursor);
                            safeWriteCursor += soundOutput.safetyBytes;

                            bool32 audioCardIsLowLatency = (safeWriteCursor < expectedFrameBoundaryByte);

                            DWORD targetCursor = 0;
                            if (audioCardIsLowLatency)
                            {
                                targetCursor = (expectedFrameBoundaryByte + expectedSoundBytesPerFrame);
                            }
                            else
                            {
                                targetCursor = (writeCursor + expectedSoundBytesPerFrame + soundOutput.safetyBytes);
                            }
                            targetCursor %= soundOutput.secondaryBufferSize;

                            DWORD bytesToWrite = 0;
                            if (byteToLock > targetCursor)
                            {
                                bytesToWrite = soundOutput.secondaryBufferSize - byteToLock;
                                bytesToWrite += targetCursor;
                            }
                            else
                            {
                                bytesToWrite = targetCursor - byteToLock;
                            }

                            game_sound_output_buffer soundBuffer = {};
                            soundBuffer.samplesPerSecond = soundOutput.samplesPerSecond;
                            soundBuffer.sampleCount = bytesToWrite / soundOutput.bytesPerSample;
                            soundBuffer.samples = samples;
                            GameGetSoundSamples(&gameMemory, &soundBuffer);

#if HANDMADE_INTERNAL
                            win32_debug_time_marker *marker = &debugTimeMarkers[debugTimeMarkerIndex];
                            marker->outputPlayCursor = playCursor;
                            marker->outputWriteCursor = writeCursor;
                            marker->outputLocation = byteToLock;
                            marker->outputByteCount = bytesToWrite;
                            marker->expectedFlipPlayCursor = expectedFrameBoundaryByte;

                            DWORD unwrappedWriteCursor = writeCursor;
                            if (unwrappedWriteCursor < playCursor)
                            {
                                unwrappedWriteCursor += soundOutput.secondaryBufferSize;
                            }
                            audioLatencyBytes = unwrappedWriteCursor - playCursor;
                            audioLatencySeconds = (((real32)audioLatencyBytes / (real32)soundOutput.bytesPerSample) / (real32)soundOutput.samplesPerSecond);

                            char textBuffer[256];
                            _snprintf_s(textBuffer, sizeof(textBuffer),
                                        "BTL:%u TC:%u BTW:%u - PC:%u WC:%u DELTA:%u (%fs)\n",
                                        byteToLock, targetCursor, bytesToWrite,
                                        playCursor, writeCursor, audioLatencyBytes, audioLatencySeconds);
                            OutputDebugStringA(textBuffer);
#endif
                            Win32FillSoundBuffer(soundOutput, byteToLock, bytesToWrite, soundBuffer);
                        }
                        else
                        {
                            soundIsValid = false;
                        }

                        LARGE_INTEGER workCounter = Win32GetWallClock();
                        real32 workSecondeElapsed = Win32GetSecondsElapsed(lastCounter, workCounter);

                        real32 secondsElapsedForFrame = workSecondeElapsed;
                        if (secondsElapsedForFrame < targetSecondsPerFrame)
                        {
                            if (sleepIsGranular)
                            {
                                DWORD sleepMS = (DWORD)(1000.0f * (targetSecondsPerFrame - secondsElapsedForFrame));

                                if (sleepMS > 0)
                                {
                                    Sleep(sleepMS);
                                }
                            }

                            real32 testSecondsElapsedForFrame = Win32GetSecondsElapsed(lastCounter, Win32GetWallClock());

                            if (testSecondsElapsedForFrame < targetSecondsPerFrame)
                            {
                                // TODO: Log
                            }

                            while (secondsElapsedForFrame < targetSecondsPerFrame)
                            {
                                secondsElapsedForFrame = Win32GetSecondsElapsed(lastCounter, Win32GetWallClock());
                            }
                        }
                        else
                        {
                            // TODO: MISSED FRAME RATE!
                            // TODO: Logging
                        }

                        LARGE_INTEGER endCounter = Win32GetWallClock();
                        real32 msPerFrame = 1000.0f * Win32GetSecondsElapsed(lastCounter, endCounter);
                        lastCounter = endCounter;

                        win32_window_dimension dimension = Win32GetWindowDimension(hWindow);
#if HANDMADE_INTERNAL
                        Win32DebugSyncDisplay(&gBackbuffer, ArrayCount(debugTimeMarkers), debugTimeMarkers,
                                              debugTimeMarkerIndex - 1, &soundOutput, targetSecondsPerFrame);
#endif
                        Win32DisplayBufferInWindow(gBackbuffer, deviceContext, dimension.width, dimension.height);

                        flipWallClock = Win32GetWallClock();
#if HANDMADE_INTERNAL
                        {
                            DWORD playCursor;
                            DWORD writeCursor;
                            if (gSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor) == DS_OK)
                            {
                                Assert(debugTimeMarkerIndex < ArrayCount(debugTimeMarkers));
                                win32_debug_time_marker *marker = &debugTimeMarkers[debugTimeMarkerIndex];
                                marker->flipPlayCursor = playCursor;
                                marker->flipWriteCursor = writeCursor;
                            }
                        }
#endif

                        game_input *temp = newInput;
                        newInput = oldInput;
                        oldInput = temp;

                        uint64 endCycleCount = __rdtsc();
                        uint64 cyclesElapsed = endCycleCount - lastCycleCount;
                        lastCycleCount = endCycleCount;

                        real64 fps = 0.0f;
                        real64 mcPerFrame = ((real64)cyclesElapsed / (1000.0f * 1000.0f));

                        char fpsBuffer[256];
                        _snprintf_s(fpsBuffer, sizeof(fpsBuffer), "%.02fms/f,  %.02f FPS,  %.02f mega cycles/f\n", msPerFrame, fps, mcPerFrame);
                        OutputDebugStringA(fpsBuffer);
#if HANDMADE_INTERNAL
                        ++debugTimeMarkerIndex;
                        if (debugTimeMarkerIndex == ArrayCount(debugTimeMarkers))
                        {
                            debugTimeMarkerIndex = 0;
                        }
#endif
                    }
                }
            }
            else
            {
                // TODO: Logging
            }
        }
        else
        {
        }
    }
    else
    {
        // TODO: Logging
    }
}